package com.utopia.keyvalue.store.impl.disk;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.utopia.keyvalue.store.index.FileStoreTest;

/**
 * @author yanjiaqiang
 * @date 2011-3-17
 * @version $Id: FileStore.java 5 2011-03-22 09:23:42Z JiaqiangYan@gmail.com $
 */
public class FileStore {

	private static Logger logger = LoggerFactory.getLogger(FileStoreTest.class);

	RandomAccessFile raf;
	FileChannel fileChannel;
	MappedByteBuffer fileBuf;

	// 4bit(文件总记录数)|4bit(文件记录的开始位置)|4bit(文件写的index)
	private final static int RecordSizeOffset = 0;
	private final static int StartOffset = 4;
	private final static int WriteOffset = 8;
	private static int filesize;

	public FileStore(File file, int size) {
		if (size < 1024 * 1024) {
			throw new IllegalArgumentException(" fileSize can't be less than 1024*1024");
		}
		try {
			if (file.exists()) {
				raf = new RandomAccessFile(file, "rw");
				fileChannel = raf.getChannel();
				filesize = (int) Math.max(size, file.length());
				fileBuf = fileChannel.map(MapMode.READ_WRITE, 0, filesize);
				if (file.length() == 0) {
					updateFileHead(0, 12, 12);
				}
			} else {
				if (file.createNewFile()) {
					raf = new RandomAccessFile(file, "rw");
					fileChannel = raf.getChannel();
					fileBuf = fileChannel.map(MapMode.READ_WRITE, 0, size);
					filesize = size;
					updateFileHead(0, 12, 12);
				}
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	private void updateFileHead(int recordSize, int startIndex, int writeIndex) {
		fileBuf.position(0);
		fileBuf.putInt(recordSize);
		fileBuf.putInt(startIndex);
		fileBuf.putInt(writeIndex);
		fileBuf.position(writeIndex);
	}

	/**
	 * append the data to the file tail
	 * 
	 * @param data
	 * @return
	 */
	public boolean put(byte[] data) {
		return put(getWriteOffset(), data);
	}

	public boolean put(int offset, byte[] data) {
		while (getFileSize() < getWriteOffset() + data.length) {
			incrementFileSize(getFileSize() * 2);
		}
		fileBuf.position(offset);
		fileBuf.put(data);
		updateFileHead(getRecordSize() + 1, getStartOffset(), getWriteOffset() + data.length);
		return true;
	}

	public byte[] get(int offset, int length) {
		if (offset < getStartOffset()) {
			throw new IllegalArgumentException("index ");
		}
		if (length < 1) {
			throw new IllegalArgumentException("length can't less than 1");
		}

		byte[] data = null;
		int fileSize = getFileSize();
		if (offset >= fileSize) {
			return new byte[0];
		}
		if (offset + length > fileSize) {
			data = new byte[fileSize - offset];
		} else {
			data = new byte[length];
		}
		fileBuf.position(offset);
		fileBuf.get(data);
		return data;
	}

	public boolean incrementFileSize(int newSize) {
		try {
			if (getFileSize() < newSize) {
				fileBuf = fileChannel.map(MapMode.READ_WRITE, 0, newSize);
				fileBuf.position(getWriteOffset());
				filesize = newSize;
			}
			return true;
		} catch (IOException e) {
			e.printStackTrace();
		}
		return false;
	}

	public void force() {
		fileBuf.force();
	}

	public void close() {
		fileBuf.force();
		fileBuf = null;
		try {
			fileChannel.close();
			fileChannel = null;
		} catch (IOException e) {
			logger.error(e.getMessage(), e);
		}
		try {
			raf.close();
			raf = null;
		} catch (IOException e) {
			logger.error(e.getMessage(), e);
		}
	}

	public int getStartOffset() {
		fileBuf.position(StartOffset);
		int startIndex = fileBuf.getInt();
		fileBuf.position(getWriteOffset());
		return startIndex;
	}

	public int getWriteOffset() {
		fileBuf.position(WriteOffset);
		int writeIndex = fileBuf.getInt();
		fileBuf.position(writeIndex);
		return writeIndex;
	}

	public int getFileSize() {
		return filesize;
	}

	public int getRecordSize() {
		fileBuf.position(RecordSizeOffset);
		int recordSize = fileBuf.getInt();
		fileBuf.position(getWriteOffset());
		return recordSize;
	}

}
